Borland Online And The Cobb Group Present:


August, 1994 - Vol. 1 No. 8

C++ Programming - More on iterator classes

In last month's Borland C++ Developer's Journal, we described the basic design principles behind iterator classes and we showed you how to implement forward and reverse iterator classes for your own container classes (C++ Programming - An introduction to iterator classes). Unfortunately, writing your own container classes from scratch is a time-consuming and error-prone process.

In this article, we'll show you how Borland's Object-based Container class library (in which all classes derive from the Object class) implements iterators for the container classes, and how you can use these iterators directly or indirectly in your own programs. In a future issue, we'll close this series by showing you how you can use the template-based BIDS class library's iterator classes.

Internal and external iterators

In the ITERATOR.EXE program from last month's article, you'll recall that we created an integer array class, a class to iterate forward through the array, and a class to iterate through the array in the opposite direction. To keep the design of the integer array class simple, we didn't include any iterator member functions in the class.

In contrast, many of Borland's container classes combine iterator member functions with iterator classes. If a container class creates its own iterator object when you call an iterator member function, that iterator object is known as an internal iterator. Many of the container classes in the Borland class libraries use internal iterators to implement iterating functions.

For example, if you create a test function that you want to apply to each item in a container, you can call the firstThat() or the lastThat() member function of the Container class. The member function will call that test function for each item in the container and return a reference to the first or last object that matches the test condition. To implement these functions, the container creates an internal iterator object, uses the iterator, and then destroys it before the function returns the result.

If, instead, you want to create an iterator object that exists independently of its container object, it's called an external iterator. The container classes that promote this in Borland's Container class libraries provide an initIterator() member function that creates an external iterator and associates it with its corresponding container object.

Intrusive and non-intrusive iterators

When you're designing an iterator class that allows external iterator objects, you'll need to decide whether the container or the iterator will keep track of the iterator's position in the container. An intrusive iterator relies on the container class to maintain the current iterator position. A non-intrusive iterator keeps track of its own position. Unfortunately, intrusive iterators make it very difficult to manage multiple, simultaneous iterators; since the container maintains the iterator position, each iterator has to be in the same position in the container.

Because certain operations require that you use the least possible communication (i.e. function calls) between the iterator and the container, an intrusive iterator is sometimes necessary. However, when a general-purpose approach is more important than absolute speed, iterators that maintain the current iterator position themselves (non-intrusive iterators) are more common. Since Borland wanted the Container class library to be useful in many situations, all the iterator types in the Container class library are non-intrusive iterators.

Forward and reverse iteration

In last month's article, we created a forward iterator class first and then derived the reverse iterator class from it. By doing this, we kept the interface to the iterator classes simple­­to iterate, we simply called the function call operator.

In keeping the interface simple, though, we made it impossible to change the iteration direction without creating an iterator of the opposite class. In contrast, Borland overloads the pre- and post-increment operators for forward iterators. Then, instead of creating a specific class for reverse iteration, Borland sometimes derives new classes from the forward iterator classes. The derived iterators provide iteration in either direction by overloading the pre- and post-decrement operators as well.

Overloading an iterator class's pre- and post-increment or pre- and post-decrement operators lets you move an iterator to point to the next item before or after returning the current item. While this increases the complexity of the class's interface somewhat, it adds to the flexibility you have when you're adjusting the position of the iterator.

Iterating a DoubleList

Let's create a program similar in function to last month's ITERATOR.EXE. However, in this program, we'll take advantage of some classes from Borland's Container class library.

To begin, launch the Borland C++ Integrated Development Environment (IDE) 3.1 for DOS. When the IDE's main menu bar and desktop appear, choose New from the File menu and enter the code from Listing A.


Listing A: STRNITER.CPP

#include <dbllist.h>
#include <strng.h>
#include <iostream.h>
 
int
main()
{
  DoubleList list;

  list.addAtTail(*(new String("a")));
  list.addAtTail(*(new String("b")));
  list.addAtTail(*(new String("c")));
  list.addAtTail(*(new String("d")));
  list.addAtTail(*(new String("e")));
  list.addAtTail(*(new String("f")));

  ContainerIterator& forwardIterator =
    list.initIterator();
  DoubleListIterator 
    reverseIterator(list, 0);

  cout << "FWD REV" << endl;

  while(forwardIterator && reverseIterator)
  {
    cout << " " << forwardIterator++;
    cout << "    " << reverseIterator-- << endl; 
  } 
  return 0;
}

When you finish entering the code, choose Save from the File Menu. Then, in the Save File As entry field of the Save File As dialog box, enter STRNITER.CPP and click OK.

Next, choose Linker from the Options menu and then choose Libraries... from the Linker submenu. When the Libraries dialog box appears, select the Static radio button in the Container Class Library group to enable the Linker to link in the correct functions from this library. Click OK to save this setting.

Next, choose Directories... from the Options menu. In the Include Directories entry field, add the path

\BORLANDC\CLASSLIB\INCLUDE;

In the Libraries Directories entry field, make sure that

\BORLANDC\CLASSLIB\LIB;

is one of the paths shown. If necessary, enter this path (add ; to the end of the previous path). When you finish, click OK.

To build the program, choose Compile from the Compile menu. When the Linker finishes linking the program, choose Quit from the File menu to quit the IDE.

When the DOS prompt appears, enter STRNITER. Figure A shows the output you should see from this program.


Figure A - The output from the STRNITER.EXE program illustrates the use of simultaneous iterators on a doubly linked list.

FWD REV
a   f
b   e
c   d
d   c
e   b
f   a  

In this program, we use the DoubleList class to implement a doubly linked list of String objects (the String class derives from the TObject class). After adding six text strings, we then create a forward iterator (a ContainerIterator object) and a reverse iterator (a DoubleListIterator object).

Finally, we use a simple while() loop to display each item in the list. Because each iterator's class defines an integer conversion function that returns 1 when the iterator is pointing to a valid object, the while() loop can contain the symbol names themselves to test for an end-of-list condition. Inside the loop, the overloaded versions of the post-increment and post-decrement operators return the corresponding iterator's current item in the list and then increment or decrement the iterator.

Conclusion

Iterator classes and their corresponding container classes can be very powerful. By learning to use iterator and container classes that are part of the Borland Container class library, you can add a significant amount of flexibility to your designs without writing a lot of code.

Return to the Borland C++ Developer's Journal index

Subscribe to the Borland C++ Developer's Journal


Copyright (c) 1996 The Cobb Group, a division of Ziff-Davis Publishing Company. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis Publishing Company is prohibited. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Publishing Company.